package de.hsb.ai.learning;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import junit.framework.Assert;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.hsb.ai.domain.ScotlandJadeVocabulary;
import de.hsb.ai.learning.filter.FugitiveEntryFilter;
import de.hsb.ai.util.ScotlandJadeGameLogger;
import de.hsb.ai.util.ScotlandJadeGameLogger.Entry;
import de.hsb.ai.util.map.MapInfo;
import de.hsb.ai.util.map.ScotlandJadeMap;
import de.hsb.ai.util.map.ScotlandJadeMapHelper;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex.Type;

public class Replay implements Runnable {
	
	public static final Logger logger = LoggerFactory.getLogger("sy.replay");
	
	public interface Listener {
		
		void beforeReplay(Replay replay);
		
		void handleEntry(Replay replay, Entry entry);
		
		void afterReplay(Replay replay);
		
	}
	
	private List<Listener> listeners = new LinkedList<Replay.Listener>();

	private ScotlandJadeMap map;
	private MapInfo<ScotlandJadeVertex, ScotlandJadeEdge> info;
	private List<Entry> entries;
	private List<ScotlandJadeVertex> currentPositions;
	private List<ScotlandJadeVertex> initialPositions;
	private int round;

	private int lastPlayerIndex;

	private int fugitiveMoveCount;
	
	private static final int MIN_ENTRIES = 10;

	public Replay(List<Entry> entries) {
		if (entries.size() < MIN_ENTRIES)
			throw new IllegalArgumentException("not enaugh entries: " + entries.size());
		
		this.map = ScotlandJadeMapHelper.createMap();
		this.info = new MapInfo<ScotlandJadeVertex, ScotlandJadeEdge>(map);
		this.initialPositions = new LinkedList<ScotlandJadeVertex>();
		
		if (entries.isEmpty())
			throw new IllegalArgumentException("entries must not be empty");
		
		int i;
		
		for (i = 0; i < ScotlandJadeVocabulary.MAX_PLAYERS; ++i) {
			ScotlandJadeGameLogger.Entry entry = entries.get(i);
			if (i > 0 && entry.idx == 0) break;
			ScotlandJadeVertex vertex = this.map.get(entry.node); 
			this.initialPositions.add(vertex);
			vertex.setType(FugitiveEntryFilter.isFugitive(entry.idx) ? Type.FUGITIVE : Type.DETECTIVE);
		}
		
		this.currentPositions = new ArrayList<ScotlandJadeVertex>(initialPositions);
		
		this.entries = entries.subList(i, entries.size());
		
		logger.info("replay: " + currentPositions.size() + " players, " + entries.size() + " entries");
	}
	
	@Override
	public void run() {
		notifyBeforeReplay();
		
		round = 1;
		
		lastPlayerIndex 	= 0;
		fugitiveMoveCount	= 0;
		
		for (Entry entry : entries) {
			int playerIndex = entry.idx;
			if (playerIndex == 0) {
				if (lastPlayerIndex != 0) ++round;
				++fugitiveMoveCount;
			}
			lastPlayerIndex = playerIndex;
			handleEntry(entry);
			
			ScotlandJadeVertex pos = map.move(currentPositions.get(entry.idx).getIndex(), entry.node);
			currentPositions.set(entry.idx, pos);
			
			Assert.assertSame(pos, currentPositions.get(entry.idx));
			Assert.assertSame(pos, map.get(entry.node));
		}
		
		logger.debug("" + entries.get(entries.size() - 1));
		notifyAfterReplay();
		
		map.reset();
		int i = 0;
		for (ScotlandJadeVertex vertex : initialPositions) {
			vertex.setType(FugitiveEntryFilter.isFugitive(i++) ? Type.FUGITIVE : Type.DETECTIVE);
		}
	}
	
	public int getRound() {
		return round;
	}
	
	public MapInfo<ScotlandJadeVertex, ScotlandJadeEdge> getMapInfo() {
		return info;
	}

	public ScotlandJadeMap getMap() {
		return map;
	}
	
	public int getFugitiveMoveCount() {
		return fugitiveMoveCount;
	}
	
	public List<ScotlandJadeVertex> getPositions() {
		return Collections.unmodifiableList(currentPositions);
	}
	
	public ScotlandJadeVertex getPosition(int playerIndex) {
		return currentPositions.get(playerIndex);
	}
	
	public void addListener(Replay.Listener listener) {
		listeners.add(listener);
	}
	
	public void removeListener(Replay.Listener listener) {
		listeners.remove(listener);
	}
	
	protected void notifyBeforeReplay() {
		for (Listener l : listeners) l.beforeReplay(this);
	}
	
	protected void handleEntry(Entry entry) {
		for (Listener l : listeners) l.handleEntry(this, entry);
	}
	
	protected void notifyAfterReplay() {
		for (Listener l : listeners) l.afterReplay(this);
	}
	
}
